<!DOCTYPE html>
<html lang="en" style="width: 100%; height: 100%;">

<head>
	<meta charset="UTF-8">

	<!-- Disable pinch zooming -->
	<meta name="viewport"
		content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
</head>

<body style="margin: 0; overflow: hidden; width: 100%; height: 100%" role="document">
	<!-- TODO: Remove additional script tag once Firefox is fixed https://bugzilla.mozilla.org/show_bug.cgi?id=1737882 -->
	<script></script>
	<script async type="module">
		// @ts-check
		/// <reference lib="dom" />

		const isSafari = (
			navigator.vendor && navigator.vendor.indexOf('Apple') > -1 &&
			navigator.userAgent &&
			navigator.userAgent.indexOf('CriOS') === -1 &&
			navigator.userAgent.indexOf('FxiOS') === -1
		);

		const isFirefox = (
			navigator.userAgent &&
			navigator.userAgent.indexOf('Firefox') >= 0
		);

		const searchParams = new URL(location.toString()).searchParams;
		const ID = searchParams.get('id');
		const webviewOrigin = searchParams.get('origin');
		const onElectron = searchParams.get('platform') === 'electron';
		const disableServiceWorker = searchParams.has('disableServiceWorker');
		const expectedWorkerVersion = parseInt(searchParams.get('swVersion'));

		/**
		 * Use polling to track focus of main webview and iframes within the webview
		 *
		 * @param {Object} handlers
		 * @param {() => void} handlers.onFocus
		 * @param {() => void} handlers.onBlur
		 */
		const trackFocus = ({ onFocus, onBlur }) => {
			const interval = 250;
			let isFocused = document.hasFocus();
			setInterval(() => {
				const isCurrentlyFocused = document.hasFocus();
				if (isCurrentlyFocused === isFocused) {
					return;
				}
				isFocused = isCurrentlyFocused;
				if (isCurrentlyFocused) {
					onFocus();
				} else {
					onBlur();
				}
			}, interval);
		};

		const getActiveFrame = () => {
			return /** @type {HTMLIFrameElement | undefined} */ (document.getElementById('active-frame'));
		};

		const getPendingFrame = () => {
			return /** @type {HTMLIFrameElement | undefined} */ (document.getElementById('pending-frame'));
		};

		/**
		 * @template T
		 * @param {T | undefined | null} obj
		 * @return {T}
		 */
		function assertIsDefined(obj) {
			if (typeof obj === 'undefined' || obj === null) {
				throw new Error('Found unexpected null');
			}
			return obj;
		}

		const vscodePostMessageFuncName = '__vscode_post_message__';

		const defaultStyles = document.createElement('style');
		defaultStyles.id = '_defaultStyles';
		defaultStyles.textContent = `
			html {
				scrollbar-color: var(--vscode-scrollbarSlider-background) var(--vscode-editor-background);
			}

			body {
				background-color: transparent;
				color: var(--vscode-editor-foreground);
				font-family: var(--vscode-font-family);
				font-weight: var(--vscode-font-weight);
				font-size: var(--vscode-font-size);
				margin: 0;
				padding: 0 20px;
			}

			img, video {
				max-width: 100%;
				max-height: 100%;
			}

			a, a code {
				color: var(--vscode-textLink-foreground);
			}

			a:hover {
				color: var(--vscode-textLink-activeForeground);
			}

			a:focus,
			input:focus,
			select:focus,
			textarea:focus {
				outline: 1px solid -webkit-focus-ring-color;
				outline-offset: -1px;
			}

			code {
				color: var(--vscode-textPreformat-foreground);
			}

			blockquote {
				background: var(--vscode-textBlockQuote-background);
				border-color: var(--vscode-textBlockQuote-border);
			}

			kbd {
				background-color: var(--vscode-keybindingLabel-background);
				color: var(--vscode-keybindingLabel-foreground);
				border-style: solid;
				border-width: 1px;
				border-radius: 3px;
				border-color: var(--vscode-keybindingLabel-border);
				border-bottom-color: var(--vscode-keybindingLabel-bottomBorder);
				box-shadow: inset 0 -1px 0 var(--vscode-widget-shadow);
				vertical-align: middle;
				padding: 1px 3px;
			}

			::-webkit-scrollbar {
				width: 10px;
				height: 10px;
			}

			::-webkit-scrollbar-corner {
				background-color: var(--vscode-editor-background);
			}

			::-webkit-scrollbar-thumb {
				background-color: var(--vscode-scrollbarSlider-background);
			}
			::-webkit-scrollbar-thumb:hover {
				background-color: var(--vscode-scrollbarSlider-hoverBackground);
			}
			::-webkit-scrollbar-thumb:active {
				background-color: var(--vscode-scrollbarSlider-activeBackground);
			}
			::highlight(find-highlight) {
				background-color: var(--vscode-editor-findMatchHighlightBackground);
			}
			::highlight(current-find-highlight) {
				background-color: var(--vscode-editor-findMatchBackground);
			}`;

		/**
		 * @param {boolean} allowMultipleAPIAcquire
		 * @param {*} [state]
		 * @return {string}
		 */
		function getVsCodeApiScript(allowMultipleAPIAcquire, state) {
			const encodedState = state ? encodeURIComponent(state) : undefined;
			return /* js */`
					globalThis.acquireVsCodeApi = (function() {
						const originalPostMessage = window.parent['${vscodePostMessageFuncName}'].bind(window.parent);
						const doPostMessage = (channel, data, transfer) => {
							originalPostMessage(channel, data, transfer);
						};

						let acquired = false;

						let state = ${state ? `JSON.parse(decodeURIComponent("${encodedState}"))` : undefined};

						return () => {
							if (acquired && !${allowMultipleAPIAcquire}) {
								throw new Error('An instance of the VS Code API has already been acquired');
							}
							acquired = true;
							return Object.freeze({
								postMessage: function(message, transfer) {
									doPostMessage('onmessage', { message, transfer }, transfer);
								},
								setState: function(newState) {
									state = newState;
									doPostMessage('do-update-state', JSON.stringify(newState));
									return newState;
								},
								getState: function() {
									return state;
								}
							});
						};
					})();
					delete window.parent;
					delete window.top;
					delete window.frameElement;
				`;
		}

		/** @type {Promise<void>} */
		const workerReady = new Promise((resolve, reject) => {
			if (disableServiceWorker) {
				return resolve();
			}

			if (!areServiceWorkersEnabled()) {
				return reject(new Error('Service Workers are not enabled. Webviews will not work. Try disabling private/incognito mode.'));
			}

			const swPath = encodeURI(`service-worker.js?v=${expectedWorkerVersion}&vscode-resource-base-authority=${searchParams.get('vscode-resource-base-authority')}&remoteAuthority=${searchParams.get('remoteAuthority') ?? ''}`);
			navigator.serviceWorker.register(swPath)
				.then(async registration => {
					/**
					 * @param {MessageEvent} event
					 */
					const versionHandler = async (event) => {
						if (event.data.channel !== 'version') {
							return;
						}

						navigator.serviceWorker.removeEventListener('message', versionHandler);
						if (event.data.version === expectedWorkerVersion) {
							return resolve();
						} else {
							console.log(`Found unexpected service worker version. Found: ${event.data.version}. Expected: ${expectedWorkerVersion}`);
							console.log(`Attempting to reload service worker`);

							// If we have the wrong version, try once (and only once) to unregister and re-register
							// Note that `.update` doesn't seem to work desktop electron at the moment so we use
							// `unregister` and `register` here.
							return registration.unregister()
								.then(() => navigator.serviceWorker.register(swPath))
								.finally(() => { resolve(); });
						}
					};
					navigator.serviceWorker.addEventListener('message', versionHandler);

					const postVersionMessage = (/** @type {ServiceWorker} */ controller) => {
						controller.postMessage({ channel: 'version' });
					};

					// At this point, either the service worker is ready and
					// became our controller, or we need to wait for it.
					// Note that navigator.serviceWorker.controller could be a
					// controller from a previously loaded service worker.
					const currentController = navigator.serviceWorker.controller;
					if (currentController?.scriptURL.endsWith(swPath)) {
						// service worker already loaded & ready to receive messages
						postVersionMessage(currentController);
					} else {
						console.log(`Found unexpected service worker controller. Found: ${currentController?.scriptURL}. Expected: ${swPath}`);

						// either there's no controlling service worker, or it's an old one:
						// wait for it to change before posting the message
						const onControllerChange = () => {
							navigator.serviceWorker.removeEventListener('controllerchange', onControllerChange);
							postVersionMessage(navigator.serviceWorker.controller);
						};
						navigator.serviceWorker.addEventListener('controllerchange', onControllerChange);
					}
				}).catch(error => {
					if (!onElectron && error instanceof Error && error.message.includes('user denied permission')) {
						return reject(new Error(`Could not register service worker. Please make sure third party cookies are enabled: ${error}`));
					}
					return reject(new Error(`Could not register service worker: ${error}.`));
				});
		});

		/**
		 *  @type {import('../webviewMessages').WebviewHostMessaging}
		 */
		const hostMessaging = new class HostMessaging {

			constructor() {
				this.channel = new MessageChannel();

				/** @type {Map<string, Array<(event: MessageEvent, data: any) => void>>} */
				this.handlers = new Map();

				this.channel.port1.onmessage = (e) => {
					const channel = e.data.channel;
					const handlers = this.handlers.get(channel);
					if (handlers) {
						for (const handler of handlers) {
							handler(e, e.data.args);
						}
					} else {
						console.log('no handler for ', e);
					}
				};
			}

			postMessage(channel, data, transfer) {
				this.channel.port1.postMessage({ channel, data }, transfer);
			}

			onMessage(channel, handler) {
				let handlers = this.handlers.get(channel);
				if (!handlers) {
					handlers = [];
					this.handlers.set(channel, handlers);
				}
				handlers.push(handler);
			}

			async signalReady() {
				const start = (/** @type {string} */ parentOrigin) => {
					window.parent.postMessage({ target: ID, channel: 'webview-ready', data: {} }, parentOrigin, [this.channel.port2]);
				};

				const parentOrigin = searchParams.get('parentOrigin');

				const hostname = location.hostname;

				if (!crypto.subtle) {
					// cannot validate, not running in a secure context
					throw new Error(`'crypto.subtle' is not available so webviews will not work. This is likely because the editor is not running in a secure context (https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts).`);
				}

				// Here the `parentOriginHash()` function from `src/vs/workbench/common/webview.ts` is inlined
				// compute a sha-256 composed of `parentOrigin` and `salt` converted to base 32
				let parentOriginHash;
				try {
					const strData = JSON.stringify({ parentOrigin, salt: webviewOrigin });
					const encoder = new TextEncoder();
					const arrData = encoder.encode(strData);
					const hash = await crypto.subtle.digest('sha-256', arrData);
					const hashArray = Array.from(new Uint8Array(hash));
					const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
					// sha256 has 256 bits, so we need at most ceil(lg(2^256-1)/lg(32)) = 52 chars to represent it in base 32
					parentOriginHash = BigInt(`0x${hashHex}`).toString(32).padStart(52, '0');
				} catch (err) {
					throw err instanceof Error ? err : new Error(String(err));
				}

				if (hostname === parentOriginHash || hostname.startsWith(parentOriginHash + '.')) {
					// validation succeeded!
					return start(parentOrigin);
				}

				throw new Error(`Expected '${parentOriginHash}' as hostname or subdomain!`);
			}
		}();

		const unloadMonitor = new class {

			constructor() {
				this.confirmBeforeClose = 'keyboardOnly';
				this.isModifierKeyDown = false;

				hostMessaging.onMessage('set-confirm-before-close', (_e, data) => {
					this.confirmBeforeClose = data;
				});

				hostMessaging.onMessage('content', (_e, data) => {
					this.confirmBeforeClose = data.confirmBeforeClose;
				});

				window.addEventListener('beforeunload', (event) => {
					if (onElectron) {
						return;
					}

					switch (this.confirmBeforeClose) {
						case 'always': {
							event.preventDefault();
							event.returnValue = '';
							return '';
						}
						case 'never': {
							break;
						}
						case 'keyboardOnly':
						default: {
							if (this.isModifierKeyDown) {
								event.preventDefault();
								event.returnValue = '';
								return '';
							}
							break;
						}
					}
				});
			}

			onIframeLoaded(/** @type {HTMLIFrameElement} */ frame) {
				assertIsDefined(frame.contentWindow).addEventListener('keydown', e => {
					this.isModifierKeyDown = e.metaKey || e.ctrlKey || e.altKey;
				});

				assertIsDefined(frame.contentWindow).addEventListener('keyup', () => {
					this.isModifierKeyDown = false;
				});
			}
		};

		// state
		let firstLoad = true;
		/** @type {any} */
		let loadTimeout;
		let styleVersion = 0;

		/** @type {Array<{ readonly message: any, transfer?: ArrayBuffer[] }>} */
		let pendingMessages = [];

		const initData = {
			/** @type {number | undefined} */
			initialScrollProgress: undefined,

			/** @type {{ [key: string]: string } | undefined} */
			styles: undefined,

			/** @type {string | undefined} */
			activeTheme: undefined,

			/** @type {string | undefined} */
			themeId: undefined,

			/** @type {string | undefined} */
			themeLabel: undefined,

			/** @type {boolean} */
			screenReader: false,

			/** @type {boolean} */
			reduceMotion: false,
		};

		if (!disableServiceWorker) {
			hostMessaging.onMessage('did-load-resource', (_event, data) => {
				navigator.serviceWorker.getRegistration().then(registration => {
					assertIsDefined(registration.active).postMessage({ channel: 'did-load-resource', data }, data.data?.buffer ? [data.data.buffer] : []);
				});
			});

			hostMessaging.onMessage('did-load-localhost', (_event, data) => {
				navigator.serviceWorker.getRegistration().then(registration => {
					assertIsDefined(registration.active).postMessage({ channel: 'did-load-localhost', data });
				});
			});

			navigator.serviceWorker.addEventListener('message', event => {
				switch (event.data.channel) {
					case 'load-resource':
					case 'load-localhost':
						hostMessaging.postMessage(event.data.channel, event.data);
						return;
				}
			});
		}

		/**
		 * @param {HTMLDocument?} document
		 * @param {HTMLElement?} body
		 */
		const applyStyles = (document, body) => {
			if (!document) {
				return;
			}

			if (body) {
				body.classList.remove('vscode-light', 'vscode-dark', 'vscode-high-contrast', 'vscode-high-contrast-light', 'vscode-reduce-motion', 'vscode-using-screen-reader');

				if (initData.activeTheme) {
					body.classList.add(initData.activeTheme);
					if (initData.activeTheme === 'vscode-high-contrast-light') {
						// backwards compatibility
						body.classList.add('vscode-high-contrast');
					}
				}

				if (initData.reduceMotion) {
					body.classList.add('vscode-reduce-motion');
				}

				if (initData.screenReader) {
					body.classList.add('vscode-using-screen-reader');
				}

				body.dataset.vscodeThemeKind = initData.activeTheme;
				/** @deprecated data-vscode-theme-name will be removed, use data-vscode-theme-id instead */
				body.dataset.vscodeThemeName = initData.themeLabel || '';
				body.dataset.vscodeThemeId = initData.themeId || '';
			}

			if (initData.styles) {
				const documentStyle = document.documentElement.style;

				// Remove stale properties
				for (let i = documentStyle.length - 1; i >= 0; i--) {
					const property = documentStyle[i];

					// Don't remove properties that the webview might have added separately
					if (property && property.startsWith('--vscode-')) {
						documentStyle.removeProperty(property);
					}
				}

				// Re-add new properties
				for (const [variable, value] of Object.entries(initData.styles)) {
					documentStyle.setProperty(`--${variable}`, value);
				}
			}
		};

		/**
		 * @param {MouseEvent} event
		 */
		const handleInnerClick = (event) => {
			if (!event?.view?.document) {
				return;
			}

			const baseElement = event.view.document.querySelector('base');

			for (const pathElement of event.composedPath()) {
				/** @type {any} */
				const node = pathElement;
				if (node.tagName && node.tagName.toLowerCase() === 'a' && node.href) {
					if (node.getAttribute('href') === '#') {
						event.view.scrollTo(0, 0);
					} else if (node.hash && (node.getAttribute('href') === node.hash || (baseElement && node.href === baseElement.href + node.hash))) {
						const fragment = node.hash.slice(1);
						const decodedFragment = decodeURIComponent(fragment);
						const scrollTarget = event.view.document.getElementById(fragment) ?? event.view.document.getElementById(decodedFragment);
						if (scrollTarget) {
							scrollTarget.scrollIntoView();
						} else if (decodedFragment.toLowerCase() === 'top') {
							event.view.scrollTo(0, 0);
						}
					} else {
						hostMessaging.postMessage('did-click-link', { uri: node.href.baseVal || node.href });
					}
					event.preventDefault();
					return;
				}
			}
		};

		/**
		 * @param {MouseEvent} event
		 */
		const handleAuxClick = (event) => {
			// Prevent middle clicks opening a broken link in the browser
			if (!event?.view?.document) {
				return;
			}

			if (event.button === 1) {
				for (const pathElement of event.composedPath()) {
					/** @type {any} */
					const node = pathElement;
					if (node.tagName && node.tagName.toLowerCase() === 'a' && node.href) {
						event.preventDefault();
						return;
					}
				}
			}
		};

		/**
		 * @param {KeyboardEvent} e
		 */
		const handleInnerKeydown = (e) => {
			// If the keypress would trigger a browser event, such as copy or paste,
			// make sure we block the browser from dispatching it. Instead VS Code
			// handles these events and will dispatch a copy/paste back to the webview
			// if needed
			if (isUndoRedo(e) || isPrint(e) || isFindEvent(e) || isSaveEvent(e)) {
				e.preventDefault();
			} else if (isCopyPasteOrCut(e)) {
				if (onElectron) {
					e.preventDefault();
				} else {
					return; // let the browser handle this
				}
			} else if (!onElectron && (isCloseTab(e) || isNewWindow(e))) {
				// Prevent Ctrl+W closing window / Ctrl+N opening new window in PWA.
				// (No effect in a regular browser tab.)
				e.preventDefault();
			}

			hostMessaging.postMessage('did-keydown', {
				key: e.key,
				keyCode: e.keyCode,
				code: e.code,
				shiftKey: e.shiftKey,
				altKey: e.altKey,
				ctrlKey: e.ctrlKey,
				metaKey: e.metaKey,
				repeat: e.repeat
			});
		};
		/**
		 * @param {KeyboardEvent} e
		 */
		const handleInnerKeyup = (e) => {
			hostMessaging.postMessage('did-keyup', {
				key: e.key,
				keyCode: e.keyCode,
				code: e.code,
				shiftKey: e.shiftKey,
				altKey: e.altKey,
				ctrlKey: e.ctrlKey,
				metaKey: e.metaKey,
				repeat: e.repeat
			});
		};

		/**
		 * @param {KeyboardEvent} e
		 * @return {boolean}
		 */
		function isCopyPasteOrCut(e) {
			const hasMeta = e.ctrlKey || e.metaKey;
			const shiftInsert = e.shiftKey && e.key.toLowerCase() === 'insert';
			return (hasMeta && ['c', 'v', 'x'].includes(e.key.toLowerCase())) || shiftInsert;
		}

		/**
		 * @param {KeyboardEvent} e
		 * @return {boolean}
		 */
		function isUndoRedo(e) {
			const hasMeta = e.ctrlKey || e.metaKey;
			return hasMeta && ['z', 'y'].includes(e.key.toLowerCase());
		}

		/**
		 * @param {KeyboardEvent} e
		 * @return {boolean}
		 */
		function isPrint(e) {
			const hasMeta = e.ctrlKey || e.metaKey;
			return hasMeta && e.key.toLowerCase() === 'p';
		}

		/**
		 * @param {KeyboardEvent} e
		 * @return {boolean}
		 */
		function isFindEvent(e) {
			const hasMeta = e.ctrlKey || e.metaKey;
			return hasMeta && e.key.toLowerCase() === 'f';
		}

		/**
		 * @param {KeyboardEvent} e
		 * @return {boolean}
		 */
		function isSaveEvent(e) {
			const hasMeta = e.ctrlKey || e.metaKey;
			return hasMeta && e.key.toLowerCase() === 's';
		}

		/**
		 * @param {KeyboardEvent} e
		 * @return {boolean}
		 */
		function isCloseTab(e) {
			const hasMeta = e.ctrlKey || e.metaKey;
			return hasMeta && e.key.toLowerCase() === 'w';
		}

		/**
		 * @param {KeyboardEvent} e
		 * @return {boolean}
		 */
		function isNewWindow(e) {
			const hasMeta = e.ctrlKey || e.metaKey;
			return hasMeta && e.key.toLowerCase() === 'n';
		}

		let isHandlingScroll = false;

		/**
		 * @param {WheelEvent} event
		 */
		const handleWheel = (event) => {
			if (isHandlingScroll) {
				return;
			}

			hostMessaging.postMessage('did-scroll-wheel', {
				deltaMode: event.deltaMode,
				deltaX: event.deltaX,
				deltaY: event.deltaY,
				deltaZ: event.deltaZ,
				detail: event.detail,
				type: event.type
			});
		};

		/**
		 * @param {Event} event
		 */
		const handleInnerScroll = (event) => {
			if (isHandlingScroll) {
				return;
			}

			const target = /** @type {HTMLDocument | null} */ (event.target);
			const currentTarget = /** @type {Window | null} */ (event.currentTarget);
			if (!currentTarget || !target?.body) {
				return;
			}

			const progress = currentTarget.scrollY / target.body.clientHeight;
			if (isNaN(progress)) {
				return;
			}

			isHandlingScroll = true;
			window.requestAnimationFrame(() => {
				try {
					hostMessaging.postMessage('did-scroll', { scrollYPercentage: progress });
				} catch (e) {
					// noop
				}
				isHandlingScroll = false;
			});
		};

		function handleInnerDragStartEvent(/** @type {DragEvent} */ e) {
			if (e.defaultPrevented) {
				// Extension code has already handled this event
				return;
			}

			if (!e.dataTransfer || e.shiftKey) {
				return;
			}

			// Only handle drags from outside editor for now
			if (e.dataTransfer.items.length && Array.prototype.every.call(e.dataTransfer.items, item => item.kind === 'file')) {
				hostMessaging.postMessage('drag-start', undefined);
			}
		}

		/**
		 * @param {() => void} callback
		 */
		function onDomReady(callback) {
			if (document.readyState === 'interactive' || document.readyState === 'complete') {
				callback();
			} else {
				document.addEventListener('DOMContentLoaded', callback);
			}
		}

		function areServiceWorkersEnabled() {
			try {
				return !!navigator.serviceWorker;
			} catch (e) {
				return false;
			}
		}

		/**
		 * @param {import('../webviewMessages').UpdateContentEvent} data
		 * @return {string}
		 */
		function toContentHtml(data) {
			const options = data.options;
			const text = data.contents;
			const newDocument = new DOMParser().parseFromString(text, 'text/html');

			newDocument.querySelectorAll('a').forEach(a => {
				if (!a.title) {
					const href = a.getAttribute('href');
					if (typeof href === 'string') {
						a.title = href;
					}
				}
			});

			// Set default aria role
			if (!newDocument.body.hasAttribute('role')) {
				newDocument.body.setAttribute('role', 'document');
			}

			// Inject default script
			if (options.allowScripts) {
				const defaultScript = newDocument.createElement('script');
				defaultScript.id = '_vscodeApiScript';
				defaultScript.textContent = getVsCodeApiScript(options.allowMultipleAPIAcquire, data.state);
				newDocument.head.prepend(defaultScript);
			}

			// Inject default styles
			newDocument.head.prepend(defaultStyles.cloneNode(true));

			applyStyles(newDocument, newDocument.body);

			// Strip out unsupported http-equiv tags
			for (const metaElement of Array.from(newDocument.querySelectorAll('meta'))) {
				const httpEquiv = metaElement.getAttribute('http-equiv');
				if (httpEquiv && !/^(content-security-policy|default-style|content-type)$/i.test(httpEquiv)) {
					console.warn(`Removing unsupported meta http-equiv: ${httpEquiv}`);
					metaElement.remove();
				}
			}

			// Check for CSP
			const csp = newDocument.querySelector('meta[http-equiv="Content-Security-Policy"]');
			if (!csp) {
				hostMessaging.postMessage('no-csp-found', undefined);
			} else {
				try {
					// Attempt to rewrite CSPs that hardcode old-style resource endpoint
					const cspContent = csp.getAttribute('content');
					if (cspContent) {
						const newCsp = cspContent.replace(/(vscode-webview-resource|vscode-resource):(?=(\s|;|$))/g, data.cspSource);
						csp.setAttribute('content', newCsp);
					}
				} catch (e) {
					console.error(`Could not rewrite csp: ${e}`);
				}
			}

			// set DOCTYPE for newDocument explicitly as DOMParser.parseFromString strips it off
			// and DOCTYPE is needed in the iframe to ensure that the user agent stylesheet is correctly overridden
			return '<!DOCTYPE html>\n' + newDocument.documentElement.outerHTML;
		}

		// Also forward events before the contents of the webview have loaded
		window.addEventListener('keydown', handleInnerKeydown);
		window.addEventListener('keyup', handleInnerKeyup);
		window.addEventListener('dragenter', handleInnerDragStartEvent);
		window.addEventListener('dragover', handleInnerDragStartEvent);

		onDomReady(() => {
			if (!document.body) {
				return;
			}

			hostMessaging.onMessage('styles', (_event, data) => {
				++styleVersion;

				initData.styles = data.styles;
				initData.activeTheme = data.activeTheme;
				initData.themeLabel = data.themeLabel;
				initData.themeId = data.themeId;
				initData.reduceMotion = data.reduceMotion;
				initData.screenReader = data.screenReader;

				const target = getActiveFrame();
				if (!target) {
					return;
				}

				if (target.contentDocument) {
					applyStyles(target.contentDocument, target.contentDocument.body);
				}
			});

			// propagate focus
			hostMessaging.onMessage('focus', () => {
				const activeFrame = getActiveFrame();
				if (!activeFrame || !activeFrame.contentWindow) {
					// Focus the top level webview instead
					window.focus();
					return;
				}

				if (document.activeElement === activeFrame) {
					// We are already focused on the iframe (or one of its children) so no need
					// to refocus.
					return;
				}

				activeFrame.contentWindow.focus();
			});

			// update iframe-contents
			let updateId = 0;
			hostMessaging.onMessage('content', async (_event, /** @type {import('../webviewMessages').UpdateContentEvent} */ data) => {
				const currentUpdateId = ++updateId;
				try {
					await workerReady;
				} catch (e) {
					console.error(`Webview fatal error: ${e}`);
					hostMessaging.postMessage('fatal-error', { message: e + '' });
					return;
				}

				if (currentUpdateId !== updateId) {
					return;
				}

				const options = data.options;
				const newDocument = toContentHtml(data);

				const initialStyleVersion = styleVersion;

				const frame = getActiveFrame();
				const wasFirstLoad = firstLoad;
				// keep current scrollY around and use later
				/** @type {(body: HTMLElement, window: Window) => void} */
				let setInitialScrollPosition;
				if (firstLoad) {
					firstLoad = false;
					setInitialScrollPosition = (body, window) => {
						if (typeof initData.initialScrollProgress === 'number' && !isNaN(initData.initialScrollProgress)) {
							if (window.scrollY === 0) {
								window.scroll(0, body.clientHeight * initData.initialScrollProgress);
							}
						}
					};
				} else {
					const scrollY = frame && frame.contentDocument && frame.contentDocument.body ? assertIsDefined(frame.contentWindow).scrollY : 0;
					setInitialScrollPosition = (body, window) => {
						if (window.scrollY === 0) {
							window.scroll(0, scrollY);
						}
					};
				}

				// Clean up old pending frames and set current one as new one
				const previousPendingFrame = getPendingFrame();
				if (previousPendingFrame) {
					previousPendingFrame.setAttribute('id', '');
					document.body.removeChild(previousPendingFrame);
				}
				if (!wasFirstLoad) {
					pendingMessages = [];
				}

				const newFrame = document.createElement('iframe');
				newFrame.title = data.title;
				newFrame.setAttribute('id', 'pending-frame');
				newFrame.setAttribute('frameborder', '0');

				const sandboxRules = new Set(['allow-same-origin', 'allow-pointer-lock']);
				if (options.allowScripts) {
					sandboxRules.add('allow-scripts');
					sandboxRules.add('allow-downloads');
				}
				if (options.allowForms) {
					sandboxRules.add('allow-forms');
				}
				newFrame.setAttribute('sandbox', Array.from(sandboxRules).join(' '));

				const allowRules = ['cross-origin-isolated;', 'autoplay;'];
				if (!isFirefox && options.allowScripts) {
					allowRules.push('clipboard-read;', 'clipboard-write;');
				}
				newFrame.setAttribute('allow', allowRules.join(' '));
				// We should just be able to use srcdoc, but I wasn't
				// seeing the service worker applying properly.
				// Fake load an empty on the correct origin and then write real html
				// into it to get around this.
				const fakeUrlParams = new URLSearchParams({ id: ID });
				if (globalThis.crossOriginIsolated) {
					fakeUrlParams.set('vscode-coi', '3'); /*COOP+COEP*/
				}
				newFrame.src = `./fake.html?${fakeUrlParams.toString()}`;

				newFrame.style.cssText = 'display: block; margin: 0; overflow: hidden; position: absolute; width: 100%; height: 100%; visibility: hidden';
				document.body.appendChild(newFrame);

				newFrame.contentWindow.addEventListener('keydown', handleInnerKeydown);
				newFrame.contentWindow.addEventListener('keyup', handleInnerKeyup);

				/**
				 * @param {Document} contentDocument
				 */
				function onFrameLoaded(contentDocument) {
					// Workaround for https://bugs.chromium.org/p/chromium/issues/detail?id=978325
					setTimeout(() => {
						contentDocument.open();
						contentDocument.write(newDocument);
						contentDocument.close();
						hookupOnLoadHandlers(newFrame);

						if (initialStyleVersion !== styleVersion) {
							applyStyles(contentDocument, contentDocument.body);
						}
					}, 0);
				}

				if (!options.allowScripts && isSafari) {
					// On Safari for iframes with scripts disabled, the `DOMContentLoaded` never seems to be fired: https://bugs.webkit.org/show_bug.cgi?id=33604
					// Use polling instead.
					const interval = setInterval(() => {
						// If the frame is no longer mounted, loading has stopped
						if (!newFrame.parentElement) {
							clearInterval(interval);
							return;
						}

						const contentDocument = assertIsDefined(newFrame.contentDocument);
						if (contentDocument.location.pathname.endsWith('/fake.html') && contentDocument.readyState !== 'loading') {
							clearInterval(interval);
							onFrameLoaded(contentDocument);
						}
					}, 10);
				} else {
					assertIsDefined(newFrame.contentWindow).addEventListener('DOMContentLoaded', e => {
						const contentDocument = e.target ? (/** @type {HTMLDocument} */ (e.target)) : undefined;
						onFrameLoaded(assertIsDefined(contentDocument));
					});
				}

				/**
				 * @param {Document} contentDocument
				 * @param {Window} contentWindow
				 */
				const onLoad = (contentDocument, contentWindow) => {
					if (contentDocument && contentDocument.body) {
						// Workaround for https://github.com/microsoft/vscode/issues/12865
						// check new scrollY and reset if necessary
						setInitialScrollPosition(contentDocument.body, contentWindow);
					}

					const newFrame = getPendingFrame();
					if (newFrame && newFrame.contentDocument && newFrame.contentDocument === contentDocument) {
						const wasFocused = document.hasFocus();
						const oldActiveFrame = getActiveFrame();
						if (oldActiveFrame) {
							document.body.removeChild(oldActiveFrame);
						}
						// Styles may have changed since we created the element. Make sure we re-style
						if (initialStyleVersion !== styleVersion) {
							applyStyles(newFrame.contentDocument, newFrame.contentDocument.body);
						}
						newFrame.setAttribute('id', 'active-frame');
						newFrame.style.visibility = 'visible';

						contentWindow.addEventListener('scroll', handleInnerScroll);
						contentWindow.addEventListener('wheel', handleWheel);

						if (wasFocused) {
							contentWindow.focus();
						}

						pendingMessages.forEach((message) => {
							contentWindow.postMessage(message.message, window.origin, message.transfer);
						});
						pendingMessages = [];
					}
				};

				/**
				 * @param {HTMLIFrameElement} newFrame
				 */
				function hookupOnLoadHandlers(newFrame) {
					clearTimeout(loadTimeout);
					loadTimeout = undefined;
					loadTimeout = setTimeout(() => {
						clearTimeout(loadTimeout);
						loadTimeout = undefined;
						onLoad(assertIsDefined(newFrame.contentDocument), assertIsDefined(newFrame.contentWindow));
					}, 200);

					const contentWindow = assertIsDefined(newFrame.contentWindow);

					contentWindow.addEventListener('load', function (e) {
						const contentDocument = /** @type {Document} */ (e.target);

						if (loadTimeout) {
							clearTimeout(loadTimeout);
							loadTimeout = undefined;
							onLoad(contentDocument, this);
						}
					});

					// Bubble out various events
					contentWindow.addEventListener('click', handleInnerClick);
					contentWindow.addEventListener('auxclick', handleAuxClick);
					contentWindow.addEventListener('keydown', handleInnerKeydown);
					contentWindow.addEventListener('keyup', handleInnerKeyup);
					contentWindow.addEventListener('contextmenu', e => {
						if (e.defaultPrevented) {
							// Extension code has already handled this event
							return;
						}

						e.preventDefault();

						/** @type { Record<string, boolean>} */
						let context = {};

						/** @type {HTMLElement | null} */
						let el = e.target;
						while (true) {
							if (!el) {
								break;
							}

							// Search self/ancestors for the closest context data attribute
							el = el.closest('[data-vscode-context]');
							if (!el) {
								break;
							}

							try {
								context = { ...JSON.parse(el.dataset.vscodeContext), ...context };
							} catch (e) {
								console.error(`Error parsing 'data-vscode-context' as json`, el, e);
							}

							el = el.parentElement;
						}

						hostMessaging.postMessage('did-context-menu', {
							clientX: e.clientX,
							clientY: e.clientY,
							context: context
						});
					});

					contentWindow.addEventListener('dragenter', handleInnerDragStartEvent);
					contentWindow.addEventListener('dragover', handleInnerDragStartEvent);

					unloadMonitor.onIframeLoaded(newFrame);
				}
			});

			hostMessaging.onMessage('set-title', async (_event, data) => {
				const target = getActiveFrame();
				if (target) {
					target.title = data;
				}
			});

			// Forward message to the embedded iframe
			hostMessaging.onMessage('message', (_event, data) => {
				const pending = getPendingFrame();
				if (!pending) {
					const target = getActiveFrame();
					if (target) {
						assertIsDefined(target.contentWindow).postMessage(data.message, window.origin, data.transfer);
						return;
					}
				}
				pendingMessages.push(data);
			});

			hostMessaging.onMessage('initial-scroll-position', (_event, progress) => {
				initData.initialScrollProgress = progress;
			});

			hostMessaging.onMessage('execCommand', (_event, data) => {
				const target = getActiveFrame();
				if (!target) {
					return;
				}
				assertIsDefined(target.contentDocument).execCommand(data);
			});

			/** @type {string | undefined} */
			let lastFindValue = undefined;

			hostMessaging.onMessage('find', (_event, data) => {
				const target = getActiveFrame();
				if (!target) {
					return;
				}

				if (!data.previous && lastFindValue !== data.value && target.contentWindow) {
					// Reset selection so we start search at the head of the last search
					const selection = target.contentWindow.getSelection();
					if (selection) {
						selection.collapse(selection.anchorNode);
					}
				}
				lastFindValue = data.value;

				const didFind = (/** @type {any} */ (target.contentWindow)).find(
					data.value,
					/* caseSensitive*/ false,
					/* backwards*/ data.previous,
					/* wrapAround*/ true,
					/* wholeWord */ false,
					/* searchInFrames*/ false,
					false);
				hostMessaging.postMessage('did-find', didFind);
			});

			hostMessaging.onMessage('find-stop', (_event, data) => {
				const target = getActiveFrame();
				if (!target) {
					return;
				}

				lastFindValue = undefined;

				if (!data.clearSelection && target.contentWindow) {
					const selection = target.contentWindow.getSelection();
					if (selection) {
						for (let i = 0; i < selection.rangeCount; i++) {
							selection.removeRange(selection.getRangeAt(i));
						}
					}
				}
			});

			trackFocus({
				onFocus: () => hostMessaging.postMessage('did-focus', undefined),
				onBlur: () => hostMessaging.postMessage('did-blur', undefined)
			});

			(/** @type {any} */ (window))[vscodePostMessageFuncName] = (/** @type {string} */ command, /** @type {any} */ data) => {
				switch (command) {
					case 'onmessage':
					case 'do-update-state':
						hostMessaging.postMessage(command, data);
						break;
				}
			};

			hostMessaging.signalReady();
		});
	</script>
</body>

</html>
